package com.yz.sharkim.handler;

import com.yz.sharkim.exception.AbstractHttpException;
import com.yz.sharkim.exception.HttpNoSupportException;
import com.yz.sharkim.service.IUserService;
import com.yz.sharkim.service.impl.UserServiceImpl;
import com.yz.sharkim.util.BeanUtil;
import com.yz.sharkim.util.ConvertUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import java.util.Objects;

import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.POST;

@Slf4j
public class RouterHandler extends SimpleChannelInboundHandler {

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        if (o instanceof FullHttpRequest) {
            handleHttpRequest(channelHandlerContext, (FullHttpRequest) o);
        }
        if (o instanceof WebSocketFrame) {
            handleWebSocketFrame(channelHandlerContext, (WebSocketFrame) o);
        }
    }

    private void handleHttpRequest(ChannelHandlerContext channelHandlerContext, FullHttpRequest o) {
        val method = o.method();
        if (!o.method().equals(GET) && !o.method().equals(POST)) {
            throw new HttpNoSupportException(
                    ConvertUtil.format("Not support method {}, uri is {}", o.method(), o.uri()));
        }
        val uri = o.uri();
        if (method == GET && "ws".equals(uri)) {
            firstWebSocket(channelHandlerContext, o);
        }
        // 内容分发
        val result = RouterHttp.processRequest(o);
        val future = channelHandlerContext.writeAndFlush(
                new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK, Unpooled.copiedBuffer(ConvertUtil.toJson(result), ConvertUtil.DEFAULT_CHARSET)))
                .addListener(ChannelFutureListener.CLOSE);

        if (future.isSuccess()) {
            log.info("发送成功");
        } else {
            log.info("发送失败");
        }
    }

    private void firstWebSocket(ChannelHandlerContext ctx, FullHttpRequest o) {
        val wsFactory = new WebSocketServerHandshakerFactory("ws://127.0.0.1:1024", null, false);
        val socketServerHandShaker = wsFactory.newHandshaker(o);
        if (socketServerHandShaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        }
        Objects.requireNonNull(socketServerHandShaker).handshake(ctx.channel(), o);
    }

    private void handleWebSocketFrame(ChannelHandlerContext channelHandlerContext, WebSocketFrame o) {
        if (o instanceof TextWebSocketFrame) {
            val str = ((TextWebSocketFrame) o).text();
            log.debug("Receive: {}", str);
            RouterWebSocket.processRequest(str, channelHandlerContext.channel());
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (cause instanceof AbstractHttpException) {
            val future = ((AbstractHttpException) cause).sendException(ctx).addListener(ChannelFutureListener.CLOSE);
            if (!future.isSuccess()) {
                log.warn("Send exception error, channel id is {}, message is {}", ctx.channel().id(), cause.getMessage());
            }
        } else {
            try {
                throw cause;
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
        super.exceptionCaught(ctx, cause);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.debug("Connection disconnect ");
        val id = ctx.channel().attr(UserServiceImpl.NETTY_CHANNEL_KEY);
        if (id != null && id.get() != null) {
            val userService = BeanUtil.getBean(IUserService.class);
            if (log.isDebugEnabled()) {
                log.debug("Disconnect user is {}", userService.getUserByChannel(ctx.channel()));
            }
            userService.offline(id.get());
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.debug("New connection");
    }
}
